import copy
import json
import os
import threading
import time
import tkinter as tk
from threading import Thread
from tkinter import messagebox, simpledialog, ttk, filedialog

from agents.agent import Agent
from config import account_max_num
# from le_agent.audio import Audio, Speaker
# from le_agent.main import agent
from le_engine.config import CommonConfig
from le_engine.image_matcher import ImageMatcher
from simulators.mumu_control import MuMuControl
from ui_engine.components import MultiInputDialog, ScrollableFrame
from ui_engine.le_tools import tools
from ui_engine.task_manager import task_manager
from utils.logger import LeLogger, log
from utils.utils import account_config, EventBus, CommonUtils

CONFIG_PATH = account_config(path=True)


class SimulatorSatus:
    running = "运行中"
    stop = "未启动"
    close = "已关闭"
    error = "错误"


def get_color(status):
    if status == SimulatorSatus.running:
        return "green"
    elif status == SimulatorSatus.stop:
        return "gray"
    elif status == SimulatorSatus.close:
        return "gray"
    elif status == SimulatorSatus.error:
        return "red"
    else:
        return "gray"


class SimulatorTab(tk.Frame):

    def __init__(self, parent, simulator_control: MuMuControl, e: EventBus):
        super().__init__(parent)
        self.e = e
        self.sim_ctl = simulator_control
        self.simulators: dict = copy.deepcopy(self.init_simulator())
        self.sim_widgets = {}
        self.create_widgets()

        self.e.subscribe("config_tab_save", self.force_refresh)

    def get_simulators(self):
        return self.simulators

    def init_simulator(self):
        sims = []
        if self.sim_ctl:
            sims = self.sim_ctl.get_device_info()
            sims = self.init_status(sims)
        return sims

    def init_status(self, sims):
        for i in sims.keys():
            if sims[i].get("adb_host_ip"):
                sims[i]['status'] = SimulatorSatus.running
            else:
                sims[i]['status'] = SimulatorSatus.stop
        return sims

    def create_widgets(self):
        ctrl_frame = tk.Frame(self)
        ctrl_frame.pack(fill="x", pady=10, padx=10)

        right_frame = tk.Frame(ctrl_frame)
        right_frame.pack(side="right")
        tk.Button(right_frame, text="新增模拟器", command=self.add_simulator).pack(side="left", padx=5)
        tk.Button(right_frame, text="删除选中模拟器", command=self.remove_selected_simulator).pack(side="left", padx=5)
        tk.Button(right_frame, text="刷新", command=self.force_refresh).pack(side="left", padx=5)

        self.sim_list_frame = tk.Frame(self)
        self.sim_list_frame.pack(padx=10, pady=10)

        self.refresh_simulator_rows()

    def refresh_simulator_rows(self):
        for widget in self.sim_list_frame.winfo_children():
            widget.destroy()
        self.sim_widgets.clear()

        self.selected_sim = tk.IntVar(value=-1)

        if self.simulators:
            for idx, sim in self.simulators.items():
                self.add_simulator_row(idx, sim)
        else:
            row_frame = tk.Frame(self.sim_list_frame)
            row_frame.grid(row=0, column=0, pady=5, sticky="w")

            label = tk.Label(row_frame, text="无法获取模拟器信息\n请检查模拟器路径是否正确", width=30, fg="red", font=("SimSun", 18, "bold"))
            label.pack()

    def force_refresh(self):
        self.sim_ctl.init_config()
        self.simulators = copy.deepcopy(self.init_simulator())
        self.refresh_simulator_rows()
        self.e.publish("sim_update")

    def add_simulator_row(self, idx, sim):
        row_frame = tk.Frame(self.sim_list_frame)
        row_frame.grid(row=idx, column=0, pady=5, sticky="e")

        rb = tk.Radiobutton(row_frame, variable=self.selected_sim, value=idx,
                            # bg="#f9f9f9",          # 背景色
                            fg="#333333",          # 字体颜色
                            activebackground="#e0f0ff",  # 鼠标悬停背景
                            activeforeground="#007acc",  # 鼠标悬停字体
                            selectcolor="#cce6ff",       # 单选按钮内的颜色
                            padx=10,
                            pady=5,
                            # indicatoron=True,     # 保持为传统圆圈样式
                            borderwidth=0,
                            anchor="w",
                            highlightthickness=0)
        rb.pack(side="left")

        label = tk.Label(row_frame, text=sim["name"], width=30)
        label.pack(side="left")

        status_label = tk.Label(row_frame, text=sim["status"], width=10, bg=get_color(sim["status"]), fg="white")
        status_label.pack(side="left", padx=5)

        start_btn = tk.Button(row_frame, text="启动", command=lambda i=idx: self.start_simulator(i))
        start_btn.pack(side="left", padx=5)

        stop_btn = tk.Button(row_frame, text="关闭", command=lambda i=idx: self.stop_simulator(i))
        stop_btn.pack(side="left", padx=5)

        show_btn = tk.Button(row_frame, text="显示", command=lambda i=idx: self.show_simulator(i))
        show_btn.pack(side="left", padx=5)

        hide_btn = tk.Button(row_frame, text="隐藏", command=lambda i=idx: self.hide_simulator(i))
        hide_btn.pack(side="left", padx=5)

        install_btn = tk.Button(row_frame, text="安装app", command=lambda i=idx: self.install_app(i))
        install_btn.pack(side="left", padx=5)

        screen_shot_btn = tk.Button(row_frame, text="四点截图", command=lambda i=idx: self.screen_shot(i))
        screen_shot_btn.pack(side="left", padx=5)

        screen_shot_two_btn = tk.Button(row_frame, text="两点截图", command=lambda i=idx: self.screen_shot_two(i))
        screen_shot_two_btn.pack(side="left", padx=5)

        self.sim_widgets[idx] = {
            "status": status_label,
            "start_btn": start_btn,
            "stop_btn": stop_btn
        }

    def update_status(self, idx, status, color):
        widget = self.sim_widgets[idx]["status"]
        widget.config(text=status, bg=color)

    def start_simulator(self, idx):
        if self.sim_ctl:
            result = self.sim_ctl.run_device(idx)
            if result.get("errcode") == 0:
                self.update_status(idx, SimulatorSatus.running, get_color(SimulatorSatus.running))
            else:
                self.update_status(idx, SimulatorSatus.error, get_color(SimulatorSatus.error))
        else:
            Thread(target=self.fake_start_process, args=(idx,)).start()
        self.force_refresh()

    def stop_simulator(self, idx):
        if self.sim_ctl:
            result = self.sim_ctl.shutdown_devices(idx)
            if result.get("errcode") == 0:
                self.update_status(idx, SimulatorSatus.stop, get_color(SimulatorSatus.stop))
            else:
                self.update_status(idx, SimulatorSatus.error, get_color(SimulatorSatus.error))
        else:
            self.update_status(idx, SimulatorSatus.close, get_color(SimulatorSatus.close))
        self.force_refresh()

    def show_simulator(self, idx):
        if self.sim_ctl:
            self.sim_ctl.show_devices(index=idx)
        else:
            log.info(f"显示模拟器 {self.simulators[idx]['name']}")

    def hide_simulator(self, idx):
        if self.sim_ctl:
            self.sim_ctl.hide_devices(index=idx)
        else:
            log.info(f"隐藏模拟器 {self.simulators[idx]['name']}")

    def install_app(self, idx):
        file_path = filedialog.askopenfilename(
            title="选择 APK 文件",
            filetypes=[("APK 文件", "*.apk")],
            defaultextension=".apk"
        )

        if not file_path:
            return

        device_info = self.sim_ctl.get_app_info(idx)
        if device_info.get("state") == "not_installed":
            result = self.sim_ctl.install_app(idx, file_path)
            if result.get("package") == CommonConfig.package_name:
                messagebox.showinfo("", "安装成功")
            else:
                log.error(f"安装失败失败 {result}")
                messagebox.showerror("失败", result.get("result"))
        else:
            messagebox.showinfo("", "该应用已经安装")

    def screen_shot(self, idx):
        sim = self.simulators[idx]
        im = ImageMatcher(ip=sim.get("adb_host_ip"), port=sim.get("adb_port"))
        result = MultiInputDialog(self, {"x": "x", "y": "y", "w": "宽", "h": "高", "name": "名称"}).get_result()
        if result:
            ok, shot_path = im.save_template(**result)
            if ok:
                if os.path.exists(shot_path):
                    messagebox.showinfo("", f"保存截图成功: {shot_path}")
                else:
                    messagebox.showerror("", "保存截图失败")
            else:
                messagebox.showerror("", "文件名已存在，换个名字吧，宝。")
                self.screen_shot(idx)

    def screen_shot_two(self, idx):
        sim = self.simulators[idx]
        ip = sim.get("adb_host_ip")
        port = sim.get("adb_port")

        if not ip or not port:
            messagebox.showerror("Error", "模拟器未启动！")
            return

        im = ImageMatcher(ip=ip, port=port)
        result = MultiInputDialog(self, {"point1": "第一个点", "point2": "第二个点", "name": "名称"}).get_result()

        sc_width = im.client.info['displayWidth']
        sc_height = im.client.info['displayHeight']
        if result:
            point1 = [float(i.strip()) for i in result["point1"].split(",")]
            point2 = [float(i.strip()) for i in result["point2"].split(",")]
            x, y = point1[0] * sc_width, point1[1] * sc_height
            w = (point2[0] - point1[0]) * sc_width
            h = (point2[1] - point1[1]) * sc_height
            ok, shot_path = im.save_template(x, y, w, h, result.get("name"))
            if ok:
                if os.path.exists(shot_path):
                    messagebox.showinfo("", f"保存截图成功: {shot_path}")
                else:
                    messagebox.showerror("", "保存截图失败")
            else:
                messagebox.showerror("", "文件名已存在，换个名字吧，宝。")
                self.screen_shot_two(idx)


    def fake_start_process(self, idx):
        time.sleep(2)
        self.update_status(idx, SimulatorSatus.running, get_color(SimulatorSatus.running))


    def add_simulator(self):
        if len(self.simulators) >= account_max_num:
            messagebox.showinfo("", f"上限为{account_max_num}个模拟器")
        else:
            name = simpledialog.askstring("新增模拟器", "请输入模拟器名称：")
            if self.sim_ctl:
                result = self.sim_ctl.new_devices(name)
                if result.get("errcode") == 0:
                    self.force_refresh()

    def remove_selected_simulator(self):
        idx = str(self.selected_sim.get())
        if idx in self.simulators.keys():
            if self.sim_ctl:
                del self.simulators[idx]
                self.sim_ctl.delete_device(idx)
                self.force_refresh()


class ConfigTab(tk.Frame):

    def __init__(self, parent, e: EventBus):
        super().__init__(parent)
        self.e = e
        self.config_data = {}
        self.account_entries = []
        self.cage_entries = []
        self.mumu_dir_var = tk.StringVar()
        self.dig_w_t = tk.IntVar()
        self.s_lineup = tk.IntVar()

        self.load_config()
        self.create_widgets()
        self.create_right_widgets()

    def account_config(self):
        return self.config_data["account"]

    def load_config(self):
        if not os.path.exists(CONFIG_PATH):
            self.config_data = {"account": [], "mumu_dir": ""}
            return
        with open(CONFIG_PATH, "r", encoding="utf-8") as f:
            self.config_data = json.load(f)

        self.s_lineup.set(self.config_data.get("s_lineup", 0))

    def save_config(self):
        with open(CONFIG_PATH, "w", encoding="utf-8") as f:
            json.dump(self.config_data, f, indent=2, ensure_ascii=False)
        messagebox.showinfo("保存成功", "配置已保存")
        self.e.publish("config_tab_save")

    def create_widgets(self):

        left = tk.Frame(self)
        left.grid(row=0, column=0, sticky="nsew")

        left_row_1 = tk.Frame(left)
        left_row_1.pack(side="top")
        tk.Label(left_row_1, text="MuMu 安装路径:").pack(anchor="w", padx=10, pady=5)
        path_entry = tk.Entry(left_row_1, textvariable=self.mumu_dir_var, width=45)
        path_entry.pack(anchor="w", padx=10)
        self.mumu_dir_var.set(self.config_data.get("mumu_dir", ""))

        tk.Label(left, text="账号列表:").pack(anchor="w", padx=10, pady=(10, 5))
        self.account_frame = tk.Frame(left)
        self.account_frame.pack(anchor="w", padx=10)

        for acc in self.config_data.get("account", []):
            self.add_account_row(acc["username"], acc["psw"])

        btn_frame = tk.Frame(left)
        btn_frame.pack(anchor="w", padx=10, pady=10)
        tk.Button(btn_frame, text="新增账号", command=self.add_account_row).pack(side="left", padx=5)
        tk.Button(btn_frame, text="保存配置", command=self.update_config).pack(side="left", padx=5)

    def create_right_widgets(self):
        right = tk.Frame(self)
        right.grid(row=0, column=1, sticky="nsew")

        row_1 = tk.Frame(right)
        row_1.grid(row=0, column=0, sticky="nsew")
        tk.Label(row_1, text="挖矿轮询时间:", width=15).grid(row=0, column=0, sticky="w", pady=10)
        tk.Entry(row_1, textvariable=self.dig_w_t, width=5).grid(row=0, column=1, padx=5, pady=10)
        self.dig_w_t.set(self.config_data.get("dig_w_t", 30))

        row_2 = tk.Frame(right)
        row_2.grid(row=1, column=0, sticky="nsew")
        tk.Label(row_2, text="挖矿是否补充阵容:", width=15).grid(row=0, column=0, sticky="w", pady=10)

        rb1 = ttk.Radiobutton(row_2, text="补", variable=self.s_lineup, value=1)
        rb1.grid(row=0, column=1, sticky="w", pady=10, padx=5)
        rb2 = ttk.Radiobutton(row_2, text="不补", variable=self.s_lineup, value=0)
        rb2.grid(row=0, column=2, sticky="w", pady=10, padx=5)

        row3 = tk.Frame(right, borderwidth=2, relief="groove")
        row3.grid(row=2, column=0, sticky="nsew")
        self.cage_row = row3
        tk.Label(row3, text="牢笼阵型配置列表:").grid(row=0, column=0, pady=5, sticky="w")
        for name, lineup in self.config_data.get("lineup_config", {}).items():
            self.add_cage_row(name, lineup)

        btn_frame = tk.Frame(right)
        btn_frame.grid(row=3, column=0, sticky="w")
        tk.Button(btn_frame, text="新增阵形", command=self.add_cage_row).grid(row=0, column=0)

    def add_cage_row(self, name="", lineup=""):
        if len(self.cage_entries) >= 5:
            messagebox.showinfo("", "上限为5个！")
            return

        row_num = len(self.cage_entries) + 1
        if name == "":
            added_cages = [i[0].cget("text") for i in self.cage_entries]
            for i in range(5):
                name = f"牢笼{i + 1}"
                if name not in added_cages:
                    row_num = i + 1
                    break
        else:
            row_num = int(name[-1])

        row = tk.Frame(self.cage_row)
        row.grid(row=row_num, column=0)

        name_label = tk.Label(row)
        name_label.grid(row=0, column=0, pady=3)

        name_label.config(text=name)

        lineup_entry = tk.Entry(row, width=15)
        lineup_entry.insert(0, lineup)
        lineup_entry.grid(row=0, column=1, padx=10, pady=3)

        rm_btn = tk.Button(row, text="删除", command=lambda r=row: self.remove_cage_row(row, name_label.cget("text")))
        rm_btn.grid(row=0, column=2, padx=10, pady=3)

        self.cage_entries.append([name_label, lineup_entry])

    def add_account_row(self, username="", password=""):
        if len(self.account_entries) >= account_max_num:
            messagebox.showinfo("", f"上限为{account_max_num}个！")
            return
        row = tk.Frame(self.account_frame)
        row.pack(fill="x", pady=2)

        user_entry = tk.Entry(row, width=20)
        user_entry.insert(0, username)
        user_entry.pack(side="left", padx=2)

        psw_entry = tk.Entry(row, width=20, show="*")
        psw_entry.insert(0, password)
        psw_entry.pack(side="left", padx=2)

        rm_btn = tk.Button(row, text="删除", command=lambda r=row: self.remove_account_row(r))
        rm_btn.pack(side="left", padx=2)

        self.account_entries.append((row, user_entry, psw_entry))

    def remove_account_row(self, row):
        for i, (r, _, _) in enumerate(self.account_entries):
            if r == row:
                self.account_entries.pop(i)
                break
        row.destroy()

    def remove_cage_row(self, row, name):
        for i, (n_l, l_e) in enumerate(self.cage_entries):
            if n_l.cget("text") == name:
                if len(self.cage_entries) <= 1:
                    messagebox.showerror("Error", "至少保存一个牢笼")
                    return
                else:
                    self.cage_entries.pop(i)
                    break
        row.destroy()

    def update_config(self):
        accounts = []
        for _, user_entry, psw_entry in self.account_entries:
            username = user_entry.get().strip()
            password = psw_entry.get().strip()
            if username:
                accounts.append({"username": username, "psw": password})

        cages = {}
        for n_l, l_e in self.cage_entries:
            name = n_l.cget("text")
            lineup = l_e.get()
            cages[name] = lineup

        self.config_data["account"] = accounts
        self.config_data["mumu_dir"] = self.mumu_dir_var.get().strip()
        self.config_data["dig_w_t"] = self.dig_w_t.get()
        self.config_data["s_lineup"] = self.s_lineup.get()
        self.config_data["lineup_config"] = cages
        self.save_config()


class LogAreaTab(tk.Frame):

    def __init__(self, parent):
        super().__init__(parent)

        self.create_widgets()

    def create_widgets(self):

        # 用于显示系统日志到前台
        log_area = tk.Text(self, wrap="word")

        scrollbar = tk.Scrollbar(self, orient="vertical", command=log_area.yview)

        # 配置 Text 的 yscrollcommand
        log_area.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        log_area.pack(expand=True, fill=tk.BOTH)

        LeLogger.add_text_handler(log, log_area)


class MainPanel(tk.Frame):

    def __init__(self, parent, config_tab: ConfigTab, simulator: SimulatorTab, get_task_map, e: EventBus):
        super().__init__(parent)
        self.e = e
        self.config_tab = config_tab
        self.simulator = simulator
        self.task_map = get_task_map()

        self.account_combo = None
        self.simulator_combo = None
        self.task_combo = None
        self.task_list_frame = None
        self.task_state_labels = {}

        self.create_widgets()

        self.task_obj_map = {}

        self.e.subscribe("sim_update", self.refresh_options)

    def create_widgets(self):

        left_frame = tk.Frame(self)
        left_frame.pack(side=tk.LEFT, padx=(10, 5), pady=(10, 10))

        tk.Label(left_frame, text="选择账号:").pack(anchor="w", padx=10, pady=(10, 0))
        self.account_combo = ttk.Combobox(left_frame, state="readonly")
        self.account_combo.pack(anchor="w", fill="x", padx=10)

        tk.Label(left_frame, text="选择模拟器:").pack(anchor="w", padx=10, pady=(10, 0))
        self.simulator_combo = ttk.Combobox(left_frame, state="readonly")
        self.simulator_combo.pack(anchor="w", fill="x", padx=10)

        tk.Label(left_frame, text="选择任务:").pack(anchor="w", padx=10, pady=(10, 0))
        self.task_combo = ttk.Combobox(left_frame, state="readonly")
        self.task_combo.pack(anchor="w", fill="x", padx=10)

        btn_frame = tk.Frame(left_frame)
        btn_frame.pack(anchor="w", padx=10, pady=15)
        tk.Button(btn_frame, text="开始任务", command=self.start_task).pack(side="left", padx=5)
        # tk.Button(btn_frame, text="点击说话", command=self.collect_user_says).pack(side="left", padx=5)

        self.right_frame = tk.Frame(self)
        self.right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 5), pady=(10, 10))

        # 使用 grid，固定布局结构
        self.right_frame.grid_rowconfigure(1, weight=1)  # 让第1行（任务区）可以扩展
        self.right_frame.grid_columnconfigure(0, weight=1)

        top_tool = tk.Frame(self.right_frame)
        top_tool.grid(row=0, column=0, sticky="ew")  # 水平拉伸
        tk.Button(top_tool, text="刷新状态", command=self.refresh_status).pack(side="right", padx=5)
        tk.Button(top_tool, text="停止所有任务", command=task_manager.shutdown).pack(side="right", padx=5)
        # 任务区：动态添加任务行
        self.task_list_frame = tk.Frame(self.right_frame, bg="white")
        self.task_list_frame.grid(row=1, column=0, sticky="nsew")  # 填满其余空间

        self.refresh_options()

    def refresh_options(self):
        accounts = self.config_tab.account_config()
        simulators = self.simulator.get_simulators()
        tasks = list(self.task_map.keys())

        self.account_combo['values'] = [f"{acc['username']}" for acc in accounts]
        self.simulator_combo['values'] = [sim['name'] for _, sim in simulators.items()]
        self.task_combo['values'] = tasks
        if accounts:
            self.account_combo.current(0)
        else:
            self.account_combo.set('')
        if simulators:
            self.simulator_combo.current(0)
        else:
            self.simulator_combo.set('')
        if tasks:
            self.task_combo.current(0)
        else:
            self.task_combo.set('')

    def get_a_s_t(self) -> (str, str, str):
        return self.account_combo.get(), self.simulator_combo.get(), self.task_combo.get()

    def get_usr_psw_ip_port(self, a, s) -> (str, str, str, str or int, dict):

        target_account_config = [i for i in self.config_tab.account_config() if i['username'] == a][0]
        usr = target_account_config['username']
        psw = target_account_config['psw']

        target_simulator = [sim for _, sim in self.simulator.get_simulators().items() if sim['name'] == s][0]
        try:
            ip = target_simulator['adb_host_ip']
            port = target_simulator['adb_port']
        except KeyError:
            messagebox.showerror("Error", "模拟器未启动")
            return None, None, None, None
        return usr, psw, ip, port, target_simulator


    def collect_user_says(self):
        self.thread = threading.Thread(name="_collect_user_says", target=self._collect_user_says)
        self.thread.start()

    def _collect_user_says(self):
        speaker = Speaker()
        audio = Audio(speaker=speaker)
        input_data = audio.recognize()
        input_data = " ".join(input_data)
        log.info(input_data)
        log.info(f'思考中, 稍等...')
        if input_data:
            response = agent.ask(input_data)
        else:
            response = "没有识别到，请重新尝试"
        log.info(f"{response}")
        speaker.speak(response)

    def start_task(self):
        if len(self.task_state_labels) >= 5:
            messagebox.showerror("错误", "最多执行5个任务")
            return

        account, simulator, task = self.get_a_s_t()

        if not account or not simulator or not task:
            messagebox.showerror("错误", "请选择账号和模拟器以及任务")
            return

        usr, psw, ip, port, target_simulator = self.get_usr_psw_ip_port(account, simulator)

        for k in self.task_state_labels:
            target_sim_index = target_simulator.get("index")
            target_sim_name = target_simulator.get("name")

            if usr in k:
                _sim_name = self.simulator.sim_ctl.get_device_info(k.split("-")[2]).get("name")
                messagebox.showerror("Error", f"{usr} 正在 {_sim_name} 上 {k.split('-')[-1]}, 不可重复添加")
                return

            if target_sim_index == k.split("-")[2]:
                _usr = k.split("-")[1]
                messagebox.showerror("Error", f"{_usr} 正在 {target_sim_name} 上 {k.split('-')[-1]}, 不可重复添加")
                return

        if usr and ip:
            task_obj = self.task_map[task]
            t = task_obj(ip, port, usr, psw, sim_index=target_simulator.get("index"))
            ok, msg = t.send_task()
            if ok:
                self.add_task_row(t.name)
            else:
                messagebox.showerror("Error", msg)

        return

    def add_task_row(self, task_id):
        row = tk.Frame(self.task_list_frame, bg="yellow")
        row.pack(fill="x", pady=2, padx=2)

        task_id_split = task_id.split("-")[1:]

        sim_name = self.simulator.sim_ctl.get_device_info(task_id_split[1]).get("name")

        id_label = tk.Label(row, text=sim_name, width=15, anchor="w", bg="yellow")
        id_label.pack(side="left")
        id_label = tk.Label(row, text=task_id_split[0], width=15, anchor="w", bg="yellow")
        id_label.pack(side="left")
        id_label = tk.Label(row, text=task_id_split[2], width=15, anchor="w", bg="yellow")
        id_label.pack(side="left")

        # 状态 Label
        status_label = tk.Label(row, text=task_manager.get_status(task_id), width=15, anchor="w")
        status_label.pack(side="left")
        self.task_state_labels[task_id] = status_label

        # 停止按钮
        stop_btn = tk.Button(row, text="停止", command=lambda: task_manager.stop_task(task_id))
        stop_btn.pack(side="right", padx=3)

        # 删除按钮
        delete_btn = tk.Button(row, text="删除", command=lambda: self.delete_task_row(task_id, row))
        delete_btn.pack(side="right", padx=3)

    def delete_task_row(self, task_id, row_widget):
        # 停止任务（可选）
        task_manager.stop_task(task_id)

        # 删除 UI 行
        row_widget.destroy()

        if task_id in self.task_state_labels:
            del self.task_state_labels[task_id]

        # 删除任务管理器中记录（可选）
        if task_id in task_manager.tasks:
            del task_manager.tasks[task_id]

    def refresh_status(self):
        statuses = task_manager.get_all_statuses()
        for task_id, status in statuses.items():
            label = self.task_state_labels.get(task_id)
            if label:
                label.config(text=status)

class TextHandler:
    """模拟日志处理器，把消息写入 Text 控件"""
    def __init__(self, text_widget):
        self.text_widget = text_widget


    def write(self, message, tag=None):
        self.text_widget.config(state='normal')
        self.text_widget.insert(tk.END, message + "\n", tag)
        self.text_widget.config(state='disabled')
        self.text_widget.yview(tk.END)


class ChatArea(tk.Frame):
    """聊天区域，包括滚动消息区和输入框"""
    def __init__(self, parent):
        super().__init__(parent)
        self.create_widgets()
        self._agent = None

    @property
    def agent(self):
        if self._agent is None:
            self._agent = Agent(checkpointer=CommonUtils.resource_path(['test.sqlite']), tools=tools)
        return self._agent

    def create_widgets(self):
        # 上半部分：聊天文本框 + 滚动条
        self.chat_frame = tk.Frame(self)
        self.chat_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        # self.chat_frame.pack_propagate(False)  # 禁止子控件改变 frame 尺寸
        # self.chat_frame.config(height=300)

        self.bg_canvas = tk.Canvas(self.chat_frame, bg="#ffffff")
        self.bg_canvas.place(relx=0, rely=0, relwidth=1, relheight=1)
        self.chat_text = tk.Text(
            self.chat_frame,
            wrap="word",
            state='disabled',
            bg="#ffffff",  # 先设白色
            font=("Arial", 12),
            bd=0,           # 去掉边框
            highlightthickness=0
        )
        self.scrollbar = tk.Scrollbar(self.chat_frame, orient="vertical", command=self.chat_text.yview)
        self.chat_text.configure(yscrollcommand=self.scrollbar.set)

        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.chat_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.handler = TextHandler(self.chat_text)

        # 下半部分：输入框 + 按钮
        self.input_frame = tk.Frame(self)
        self.input_frame.pack(side=tk.BOTTOM, fill=tk.X)

        self.input_text = tk.Entry(self.input_frame, font=("Arial", 12))
        self.input_text.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 5), pady=5)
        self.input_text.bind('<Return>', self.send_message)

        self.send_button = tk.Button(self.input_frame, text="发送", command=self.send_message, bg="#2563eb", fg="white",
                                     font=("Arial", 12))
        self.send_button.pack(side=tk.RIGHT, padx=5, pady=5)

    def add_user_message(self, msg):
        self.handler.write(f"   {msg}   ", tag='user')
        # 用户消息
        self.chat_text.tag_config(
            'user',
            justify='right',
            foreground='black',  # 字体颜色黑色，更易读
            background='#a8d0ff',  # 浅蓝色气泡
            lmargin1=50,
            lmargin2=50,
            rmargin=5,
            spacing1=8,
            spacing3=12
        )

    def add_bot_message(self, msg):
        self.handler.write(f"  {msg}  ", tag='bot')
        # Bot 消息
        self.chat_text.tag_config(
            'bot',
            justify='left',
            foreground='black',  # 字体白色
            background='#f0f0f5',  # 深色气泡
            lmargin1=5,
            lmargin2=5,
            rmargin=50,
            spacing1=8,
            spacing3=12
        )

    def add_tool_message(self, msg):
        self.handler.write(f"  {msg}  ", tag='tool')
        # Bot 消息
        self.chat_text.tag_config(
            'tool',
            justify='left',
            foreground='black',  # 字体白色
            background='#ddddef',  # 深色气泡
            lmargin1=5,
            lmargin2=5,
            rmargin=50,
            spacing1=8,
            spacing3=12
        )


    def send_message(self, event=None):
        msg = self.input_text.get().strip()
        if not msg:
            return

        # 禁用输入框和按钮
        self.input_text.config(state='disabled')
        self.send_button.config(state='disabled')

        self.add_user_message(msg)
        self.input_text.delete(0, tk.END)

        try:
            # 使用线程调用耗时任务
            threading.Thread(target=self._stream_message, args=(msg,), daemon=True).start()
            # self._stream_message(msg)
        except Exception as e:
            log.error(e)
            self._enable_input()

    def _stream_message(self, msg):
        # 这里在子线程中循环 stream
        for item in self.agent.stream(msg):
            try:
                # Tkinter UI 更新必须在主线程，所以用 after
                if item["messages"][-1].type == 'human':
                    continue
                elif item["messages"][-1].type == 'ai':
                    self.after(0, self.add_bot_message, item["messages"][-1].content)
                elif item["messages"][-1].type == 'tool':
                    self.after(0, self.add_bot_message, item["messages"][-1].content)
            except Exception as e:
                log.error(e)

        self._enable_input()

    def _enable_input(self):
        self.input_text.config(state='normal')
        self.send_button.config(state='normal')
        self.input_text.focus_set()

class UpdateTab(tk.Frame):

    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent)

        self.grid_columnconfigure(0, weight=1)

        root = ScrollableFrame(self)
        root.grid(column=0, row=0, sticky="nsew", pady=10)
        self.add(root)

    def add_update_info(self, root, info):
        index = info.get("index")
        date = info.get("date")
        _list = info.get("list")
        row1 = tk.Frame(root.inner_frame, relief=tk.RIDGE, borderwidth=1)
        row1.grid(row=index, column=0, sticky="ew", padx=10, pady=10)
        tk.Label(row1, text=date).grid(row=0, column=0, sticky="w")
        for i, value in enumerate(_list):
            tk.Label(row1, text=f"{i + 1}. {value}").grid(row=i + 1, column=0, sticky="w")

    def add(self, root):
        update_info = [
            {
                "date": "2025/07/08",
                "list": [
                    "修改刷训练场无法进入附城BUG",
                    "增加小号互刷脚本",
                    "优化打包方式",
                    "根据配置的牢笼数量执行脚本",
                    "账户、模拟器数量上限增加至7个"
                ]
            },
            {
                "date": "2025/07/05",
                "list": [
                    "增加牢笼阵型配置,挖矿时不同的牢笼可以使用不同的阵容",
                    "挖矿时状态检测等待时间可以配置",
                    "挖矿时可以配置是否补充阵容",
                    "新增用户登录口令限制",
                    "修复刷功勋时被打，脚本卡断问题",
                    "新增白名单限制"
                ]
            }
        ]

        for index, info in enumerate(update_info):
            info["index"] = index
            self.add_update_info(root, info)